require 'configatron'
require 'json'

class Tripleizer
  def initialize output=nil
    @object_properties = configatron.objectProperties.to_hash
    @object_namespaches = configatron.namespaces.to_hash
    @class_map = configatron.classMap.to_hash
    @output=output
    @version="0.0.1"
    @json_hash = Hash.new
    @output_json = nil;

  end

  attr_accessor :output
  attr_reader :version
  attr_accessor :output_json

  def tripleize (tr_self,c =nil,id=nil)
    #$this->writeTriple($self,$this->uri('rdfs:comment'),'Generated by Triplify '.$this->version.' (http://Triplify.org)',true);

    #if($this->config['license'])
    #	$this->writeTriple($self,'http://creativecommons.org/ns#license',$this->config['license']);


#    configatron.queries.to_hash.each do |_class,q|
#      puts k.to_s+ " \n"
#      cols,group=nil,nil
#
#      #TODO:mehrere Unterabfragen
#
#      case configatron.LinkedDataDepth
#      when 3
#        if !id
#          return  #datentiefe von 3 gibts laut config nicht TODO:abfangen
#        end
#      when 2
#        if !c
#          write_triple uri(_class),uri('rdf:type'), uri('owl:Class')
#        end
#        #TODO continue 2
#        return
#      when 1
#
#      else
#        #TODO:DataDepthERROR
#      end
#      #TODO:some magic sql-query building
#
#      #query sql
#      #for each result make_triples
#
#    end
  end

  def search_models key
    model_groups =  eval("configatron.query").to_hash
    model_groups.each do |model_group_name,model_group|
      if model_group_name.to_s.downcase == key.downcase
        return model_group
      end
    end
  end
 
  #
  def write_model model_name,model_attributes
    puts model_name

    if model_name.to_s == "OptionValue"
      hello = "Product"
    end
    model = Model.new(model_name)   
    #dtypes =dbd_types model,model.model_attributes
    #
   
    data_types= model.get_datatypes
    #build hash
    model.get_rows.each do |item|
      line = Hash.new
      subline = Hash.new
      #and now add all mapping-attributes
      
      model.model_attributes.each do |k,v|        
        if k.to_s.include?("->")
          #find all subelements
          submodel = eval("item."+v.downcase)
          if submodel.class != (Array)
            if submodel
              sub_m = Model.new(submodel.class.to_s)
              subline[:id] = item.id
              subline[k] = eval("submodel."+sub_m.get_key.to_s)
              #subline[k.split"->"[0]] = eval("item."+k.split"->"[1]+".id")
              sub_data_types = model.get_datatypes
              extract_id_line(model.model_attributes, subline, item, sub_data_types)
              make_triples(subline, model.model_name , "", sub_data_types,false)
            end
          else
            unless submodel.empty?
              sub_m = Model.new(submodel[0].class.to_s)
              submodel.each do |submodel_item|
                subline[:id] = item.id
                subline[k] = eval("submodel_item."+sub_m.get_key.to_s)
                #subline[k.split"->"[0]] = eval("item."+k.split"->"[1]+".id")
                sub_data_types = model.get_datatypes
                extract_id_line(model.model_attributes, subline, item,sub_data_types)
                make_triples(subline, model.model_name , "", sub_data_types,false)
                subline.clear
              end
            end
          end
        else
          write_line = true;
          if v.to_s.include? "."
            write_line = nil unless eval("item."+v.to_s.split(".")[0])
          end
          line[k.to_s]=eval("item."+v.to_s) if write_line
        end
      end
      extract_id_line(model.model_attributes, line, item,data_types)

      #watch if there is a mapping to another object with "->"  
      #get triples of row
      make_triples(line, model.model_name , "", data_types)
      #render :text =>  t.make_triples(c1, controller , "", t.dbd_types)
    end
  end

  def extract_id_line model_attributes, line,item,dtypes
    #look if id is mapped to another field
    id_keys = model_attributes.to_hash.keys
    #hotfix..bad performance
    id_keys.map!{|k| k.to_s }
    id_key= id_keys.select{|k| k =~/^(ID|id|iD|Id)$/ }
    if id_key.empty?
      line[:id] = item.id
    else
      line[:id] = eval("item.#{model_attributes[id_key[0].to_sym]}")
      #set the correct datatype for it
      dtypes["id"]= dtypes[id_key[0]]
      #remove the id line
      line.delete id_key[0]
    end
  end

  def write_sql model_name, model_attributes,output
    model_attributes.each do|key,query|
      sql=  ActiveRecord::Base.connection();
      (sql.select_all query).each do |row|
        make_triples(row,model_name,"")
      end
    end
  end

  def write_rdf_head 
    # $this->writeTriple($self,$this->uri('rdfs:comment'),'Generated by Triplify '.$this->version.' (http://Triplify.org)',true);
    write_triple($base_uri, uri("rdfs:comment"), 'Generated by Triplify '+'version'+ "  (http://Triplify.org) ", "literal" )
    unless configatron.license.blank?
      #$this->writeTriple($self,'http://creativecommons.org/ns#license',$this->config['license']);
      write_triple($base_uri,'http://creativecommons.org/ns#license',configatron.license, "literal" )
    end
  end



  def make_triples (c1,rdf_class,maketype,dtypes=Array.new,rdf_type=true)
    rdf_ns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    ipref= uri rdf_class.to_s+'/'
    is_literal=false
    dtype,lang,ret="","",""
    object_property = nil

    unless c1[:id]
      id_row = c1.select {|k,v| k =~ /^(id|ID|iD|Id)$/}     
      c1[:id] = id_row[0][1]
      c1.delete id_row[0][0]

      #set datatype for id
      #dtypes[:id] = uri( (id_row[0][1]).type)
    end

    subject = uri c1[:id].to_s,ipref
    #write model :-)

    c= @class_map[rdf_class.to_sym] ?@class_map[rdf_class.to_sym]:rdf_class
    write_triple(subject, rdf_ns+"type", uri(c, @object_namespaches[:vocabulary]))  if rdf_type
          # write_triple(subject,predi    cate,object                                                  ,is_literal=false,dtype="",lang="",json=nil)
    c1.delete :id unless rdf_type

    c1.each do |k,v|
      k=k.to_s
      if v.to_s.empty?
        next
      end
      # beinhaltet key ^^ dann type oder sprache richtig setzen
      if k.index("^^")
        #TODO: k,dtype = k.split("^^")
        dtype= uri k.split("^^")[1]
        k= k.split("^^")[0]
      else if dtypes.include? k
          dtype= uri dtypes[k]
        else if k.index("@")
            lang=k.split("@")[1]
            k = k.split("@")[0]
          end
        end
      end
      # beinhaltet key -> dann muss objekt richtig gesetzt werden
      if k.index("->")
        k, object_property = k.split("->")
      else
        object_property = @object_properties[k]
      end

      #TODO
      #callbackfunktion
      #

      prop= self.uri(k,@object_namespaches[:vocabulary])

      unless object_property
        is_literal= true       
        object= v.to_s
      else
        is_literal= false
        #uri($objectProperty.($objectProperty&&substr($objectProperty,-1,1)!='/'?'/':'').$val);
        #TODO: fixme "/" in the middle
        #object= uri  "#{object_property}/#{object_property && object_property[-1,1].to_s !="/" ?"/":":"}#{v}"
        object= uri  "#{object_property}#{object_property[-1,1].to_s !="/" ? "/":":"}#{v}"
      end
     write_triple(subject,prop,object,is_literal,dtype,lang).to_s
    end    
  end

  def write_triple(subject,predicate,object,is_literal=false,dtype="",lang="")

    if @output_json
      oa = {:value=> object,:type=> is_literal ? 'literal' : 'uri'}     
      oa['datatype']=dtype if is_literal && dtype     
      oa['language']=lang if is_literal && lang      
     
      add_json_pair subject, predicate,oa
      
    else
      #(lang?"@#{lang}":'')

      #define the object
      if(is_literal)
        object = "\"#{object.to_s.gsub('"','%22')}\""+  (dtype.empty? ?  (lang.empty? ? "": "@#{lang}" ):"^^<#{dtype}>" )
      else
        object = ( object[0..1] == "_:" ) ? object : "<#{object}>"
      end
      #object="\"#{object[1..-2].gsub('"','\"')}\""
      #define the subject
      subject = ( subject[0..1] == "_:" ) ? subject : "<#{subject}>"
      if @output
        @output.write "#{subject} <#{predicate}> #{object} .\n"
      end
      "#{subject} <#{predicate}> #{object} .\n"
    end
  end

  #generates an uri with the given name
  def uri (name,default="")
    name=name.to_s
    
    if name.try(:include?, "://")
      return name[0..name.length-2] if name[-1,1]=="/"
      return name[0..name.length]
    else
      name +="/" unless (name[name.length-1..name.length-1] == ("/" || "#")) || name.try(:include?, ":")

      if name.index(":")
        #$this->ns[substr($name,0,strpos($name,':'))].$this->normalizeLocalName(substr($name,strpos($name,':')+1))
        t= @object_namespaches[name.split(":")[0].to_sym]
        t ||=""
        t += "/" unless (t[t.length-1..t.length-1] == ("/" || "#") || t.blank?) || name.try(:include?, ":")
        return  uri( t+ normalize_local_name(name.split(":")[1])+"/")
      else
        ##($default?$default:$GLOBALS['baseURI']).$this->normalizeLocalName($name));
        #falls bei default hinten ein "/" ist abschneiden
        #default= default[0.. default.length-2] unless default[default.length-1..default.length-1] == "/"
        t=  default.blank? ?  $base_uri : default
        t += "/" unless t[t.length-1..t.length-1]=="/"
        return  uri( t + normalize_local_name(name))
      end
    end
  end

  ### returns an hash with columnname as key and datatype as value
  def dbd_types (model,model_attributes)
    #hard coded mapping look mapping table
    mapping = Hash[ "binary"=>"base64Binary","boolean"=>"boolean","date"=>"date","datetime"=>"dateTime","decimal"=>"decimal","float"=>"float","integer"=>"integer","string"=>"string","text"=>"string","time"=>"time","timestamp"=>"dateTime",]
    dtypes = Hash.new
    model.columns_hash.each_key do |m|
      #make xsd datatye
      dtypes[m.to_s] ="xsd:#{mapping[model.columns_hash[m].type.to_s] }"
    end
    #replace mapping
    model_attributes.each do |k,v|
      dtypes[k.to_s]=dtypes[v.to_s]
    end
    dtypes
  end
  #some encoding stuff
  def normalize_local_name name
    CGI::escape(name).gsub(/%2F/,'/').gsub(/%23/,'#')
  end

  def json
    @json_hash.to_json
  end
  
  def add_json_pair subject, predicate,oa
    if @json_hash.key? subject
      if @json_hash[subject].key? predicate
        @json_hash[subject][predicate].merge!(oa)
      else       
        @json_hash[subject][predicate]=oa
      end      
    else
      @json_hash[subject]=Hash.new
      @json_hash[subject][predicate]=oa
    end
  end 
end
